package de.reichelt.fxrequi;

import de.reichelt.fx.ComboBoxAddon;
import de.reichelt.fx.FxForm;
import de.reichelt.fx.FxFormField;
import de.reichelt.fx.FxOption;
import de.reichelt.fxrequi.data.Artefact;
import de.reichelt.fxrequi.data.Category;
import de.reichelt.fxrequi.data.DataStore;
import de.reichelt.fxrequi.data.ProjectVersion;
import de.reichelt.fxrequi.data.ProjectX;
import de.reichelt.fxrequi.data.Requirement;
import de.reichelt.fxrequi.data.RequirementX;
import java.io.File;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import org.controlsfx.dialog.Dialogs;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;

public class RequirementController extends BaseController {
  
  //TODO 1 Anforderung(en) markieren und verschieben
  //TODO 1 Anforderung(en) markieren, kopieren und einfügen
  //TODO 1 Anforderung(en) markieren und freigeben
  //TODO 1 XML-Import implementieren
  //TODO 2 erfassen aus XML-Import
  //TODO 2 Anforderungen als PDF erzeugen
  
  @FXML private AnchorPane panList;
  @FXML private AnchorPane panDetails;
  @FXML private AnchorPane panData;
  
  @FXML private HBox panNavigation;
  @FXML private Button btnShowProject;
  @FXML private MenuItem btnNew;
  @FXML private MenuItem btnNewSub;
  @FXML private MenuItem btnImportXML;
  @FXML private MenuItem btnExportXML;
  @FXML private MenuItem btnEdit;
  @FXML private MenuItem btnDelete;
  @FXML private MenuItem btnSelect;
  @FXML private MenuItem btnRelease;
  @FXML private MenuItem btnCopy;
  @FXML private MenuItem btnPaste;
  @FXML private MenuItem btnMove;
  @FXML private MenuItem btnReports;
  @FXML private MenuItem btnOthers;
  @FXML private Button btnShowTests;
  
  @FXML private TreeTableView<Requirement> tree;
  @FXML private TreeTableColumn<Requirement,String> colCounter;
  @FXML private TreeTableColumn<Requirement,String> colTitle;
  @FXML private TreeTableColumn<Requirement,FxOption> colState;
  @FXML private TreeTableColumn<Requirement,ProjectVersion> colVersion;
  
  @FXML private Label labCreated;
  @FXML private TextField txtCounter;
  @FXML private TextField txtTitle;
  @FXML private ComboBox<Category> cmbCategory;
  @FXML private ComboBox<Artefact> cmbArtefact;
  @FXML private ComboBox<FxOption> cmbPriority;
  @FXML private ComboBox<ProjectVersion> cmbVersion;
  @FXML private ComboBox<FxOption> cmbStakeholderType;
  @FXML private TextField txtStakeholder;
  @FXML private CheckBox chkTestRequired;
  @FXML private TextArea txtSolution;
  @FXML private ComboBox<FxOption> cmbState;
  @FXML private TextArea txtWorkComment;
  @FXML private TextArea txtDescr;
  @FXML private ComboBox<FxOption> cmbWorkState;
  @FXML private TextField txtCosts;
  @FXML private TextField txtWorker;
  @FXML private TextArea txtRemark;
  @FXML private FlowPane flowKeywords;
  @FXML private ScrollPane scrollKeywords;
  
  @FXML private HBox panButtons;
  @FXML private Button btnCancel;
  @FXML private Button btnSave;
  
  private Logger log;
  private FxForm form;
  private SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
  private TreeItem<Requirement> selectedTreeItem;
  private List<String> keywordList = new ArrayList<>();

  private enum Mode {EDIT,NEW,NEWSUB}
  private Mode mode;
  
  public RequirementController() {
    super();
    log = Logger.getLogger(this.getClass().getName());
    String kwCsv = FXrequi.APP.getSelectedProject().getKeywords();
    if (kwCsv!=null) {
      String[] kwList = kwCsv.split(",");
      this.keywordList = new ArrayList<>(Arrays.asList(kwList));
    }
  }

  @Override
  public void initialize(URL url, ResourceBundle rb) {
    log.fine("aufgerufen");
    FXrequi.BUSY = true;
    try {
      log.fine("initialisiere Navigations-Buttons");
      btnShowProject.setOnAction(this::showProjectAction);
      btnShowTests.setOnAction(this::showTestsAction);
      btnNew.setOnAction(this::newRequirementAction);
      btnNewSub.setOnAction(this::newSubRequirementAction);
      btnImportXML.setOnAction(this::importXMLAction);
      btnExportXML.setOnAction(this::exportXMLAction);
      btnEdit.setOnAction(this::editRequirementAction);
      btnDelete.setOnAction(this::deleteRequirementAction);
      btnSelect.setOnAction(this::selectAction);
      btnRelease.setOnAction(this::releaseAction);
      btnCopy.setOnAction(this::copyAction);
      btnPaste.setOnAction(this::pasteAction);
      btnMove.setOnAction(this::moveAction);
      
      log.fine("initialisiere Detailbereich");
      panDetails.setDisable(true);
      cmbPriority.setItems(FXCollections.observableList(Requirement.priorities));
      new ComboBoxAddon(cmbPriority);

      cmbState.setItems(FXCollections.observableList(Requirement.states));
      new ComboBoxAddon(cmbState);
      
      cmbStakeholderType.setItems(FXCollections.observableList(Requirement.stakeholderTypes));
      new ComboBoxAddon(cmbStakeholderType);
      
      cmbWorkState.setItems(FXCollections.observableList(Requirement.workStates));
      new ComboBoxAddon(cmbWorkState);
      
      DataStore ds = DataStore.getInstance();
      cmbVersion.setItems(ds.getVersionsByProjectName(FXrequi.APP.getSelectedProject().getName()));
      new ComboBoxAddon(cmbVersion);
      
      cmbArtefact.setItems(ds.getArtefactsByProjectName(FXrequi.APP.getSelectedProject().getName()));
      new ComboBoxAddon(cmbArtefact);
      
      cmbCategory.setItems(ds.getCategories());
      new ComboBoxAddon(cmbCategory);
      
//      //zeige Hilfetext
//      flowKeywords.setOnMouseEntered(event -> {
//        ShapedPopup shapedPopup = new ShapedPopup();
//        shapedPopup.setHideOnEscape(true);
//        shapedPopup.setCloseButtonVisible(false);
//        shapedPopup.setInsets(new Insets(20));
//        shapedPopup.setPopupContent(new Group(new Label("Welcome to JideFX!")));
//        // load css if needed
//        //shapedPopup.getScene().getRoot().getStylesheets().add(ShapedPopup.class.getResource("BalloonPopup.css").toExternalForm());
//        BalloonPopupOutline outline = new BalloonPopupOutline();
//        outline.setRoundedRadius(5);
//        outline.setArrowSide(Side.RIGHT);
//        outline.setArrowHeight(60);
//        outline.setArrowWidth(20);
//        outline.setArrowBasePosition(40);
//        outline.setArrowPosition(60);
//        shapedPopup.setPopupOutline(outline);
//        shapedPopup.showPopup(flowKeywords, Pos.TOP_CENTER);
//      });

      form = new FxForm();
      form.registerButton(btnSave);
      form.registerField(txtCounter, "counter", true, true);
      form.registerField(txtTitle, "title", true, true);
      form.registerField(cmbPriority, "priority", true, true);
      form.registerField(cmbState, "state", true, true);
      form.registerField(cmbCategory, "category", false, true);
      form.registerField(cmbArtefact, "artefact", false, true);
      form.registerField(cmbVersion, "version", false, true);
      form.registerField(cmbStakeholderType, "stakeholdertype", false, true);
      form.registerField(txtStakeholder, "stakeholder", false, true);
      form.registerField(chkTestRequired, "testrequired", false, true);
      form.registerField(txtDescr, "descr", true, true);
      form.registerField(txtRemark, "remark", false, true);
      form.registerField(txtWorker, "worker", false, true);
      form.registerField(cmbWorkState, "workstate", false, true);
      form.registerField(txtWorkComment, "workcomment", false, true);
      form.registerField(txtCosts, "costs", false, true, FxFormField.SubType.INTEGER);
      form.registerField(txtSolution, "solution", false, true);
      
      //binde die Breite der Keywords FlowPane dynamisch an die Breite der ScrollPane, damit nur vertikal gescrollt werden muss
      //flowKeywords.prefWidthProperty().bind(scrollKeywords.widthProperty());
      
      btnSave.setOnAction(this::saveAction);
      btnCancel.setOnAction(this::cancelAction);
      
      //Anforderungsliste
      TreeItem<Requirement> ti = new TreeItem<>();
      ds.getRequirementsTree(ti, FXrequi.APP.getSelectedProject().getName(), null);
      tree.setShowRoot(false);
      tree.setRoot(ti);
      colCounter.setCellValueFactory(new TreeItemPropertyValueFactory("counter")); 
      colTitle.setCellValueFactory(new TreeItemPropertyValueFactory("title")); 
      colState.setCellValueFactory(new TreeItemPropertyValueFactory("state")); 
      colVersion.setCellValueFactory(new TreeItemPropertyValueFactory("projectVersion"));
      tree.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
        log.fine("tree selected item changed");
        if (!FXrequi.BUSY) {
          btnEdit.setDisable(newValue==null);
          btnDelete.setDisable(newValue==null);
          if (tree.getSelectionModel().getSelectedItem()==null) {
            setSelectedTreeItem(null);
            setFormData(null);
          } else {
            setSelectedTreeItem(newValue);
            setFormData(newValue.getValue());
          }
        }
      });
      
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim Erzeugen der Anforderungsansicht!", ex);
      FXrequi.APP.showError("Fehler beim Erzeugen der Anforderungsansicht!");
    } finally {
      FXrequi.BUSY = false;
    }
  }
  
  private void setSelectedTreeItem(TreeItem<Requirement> ti) {
    log.fine("aufgerufen: ti="+ti);
    this.selectedTreeItem = ti;
  }
  
  private void setFormData(Requirement data) {
    log.fine("aufgerufen: data="+data);
    try {
      if (data==null) {
        labCreated.setText("erzeugt ");
        form.clear();
        flowKeywords.getChildren().clear();
        cmbState.setValue(Requirement.getState("new"));
        cmbPriority.setValue(Requirement.getPriority("low"));
        
      } else {
        labCreated.setText("erzeugt am "+sdf.format(data.getDateCreated())+" von "+data.getUserCreated());
        txtCounter.setText(data.getCounter());
        txtTitle.setText(data.getTitle());
        txtDescr.setText(data.getDescr());
        cmbState.setValue(data.getState());
        cmbPriority.setValue(data.getPriority());
        cmbCategory.setValue(data.getCategory());
        cmbArtefact.setValue(data.getArtefact());
        cmbVersion.setValue(data.getProjectVersion());
        txtRemark.setText(data.getRemark());
        txtStakeholder.setText(data.getStakeholder());
        cmbStakeholderType.setValue(data.getStakeholderType());
        chkTestRequired.setSelected(data.isTestRequired());
        txtWorker.setText(data.getWorker());
        txtWorkComment.setText(data.getWorkComment());
        cmbWorkState.setValue(data.getWorkState());
        txtCosts.setText(data.getCosts()==null?"":data.getCosts().toString());
        txtSolution.setText(data.getSolution());
        flowKeywords.getChildren().clear();
        setFlowList(data.getKeywords(), keywordList); //keywordList);
      }
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim Füllen der Bedienelemente!", ex);
      FXrequi.APP.showException("Fehler beim Füllen der Bedienelemente!", ex);
    }
  }
  
  private Requirement getFormData() throws Exception {
    log.fine("aufgerufen");
    Requirement req = new Requirement();
    req.setProjectVersion((ProjectVersion)form.getField("version").getValue());
    req.setArtefact((Artefact)form.getField("artefact").getValue()); 
    req.setCounter((String)form.getField("counter").getValue());
    req.setTitle((String)form.getField("title").getValue());
    req.setDescr((String)form.getField("descr").getValue());
    req.setPriority((FxOption)form.getField("priority").getValue());
    req.setState((FxOption)form.getField("state").getValue());
    req.setStakeholder((String)form.getField("stakeholder").getValue());
    req.setStakeholderType((FxOption)form.getField("stakeholdertype").getValue());
    req.setCategory((Category)form.getField("category").getValue());
    req.setRemark((String)form.getField("remark").getValue());
    req.setTestRequired((Boolean)form.getField("testrequired").getValue());
    req.setWorker((String)form.getField("worker").getValue());
    req.setWorkState((FxOption)form.getField("workstate").getValue());
    req.setWorkComment((String)form.getField("workcomment").getValue());
    req.setSolution((String)form.getField("solution").getValue());
    req.setCosts((Integer)form.getField("costs").getValue());
    req.setKeywords(getFlowList());
    return req;
  }
  
  private void newRequirementAction(ActionEvent event) {
    log.fine("aufgerufen");
    FXrequi.BUSY = true;
    try {
      panNavigation.setDisable(true);
      panList.setDisable(true);
      panDetails.setDisable(false);
      setFormData(null);
      mode = Mode.NEW;
      setNewCounter();
      Platform.runLater(new Runnable() {@Override public void run() {txtCounter.requestFocus();}});
    } finally {
      FXrequi.BUSY = false;
    }
  }
  
  private void newSubRequirementAction(ActionEvent event) {
    log.fine("aufgerufen");
    FXrequi.BUSY = true;
    try {
      panNavigation.setDisable(true);
      panList.setDisable(true);
      panDetails.setDisable(false);
      setFormData(null);
      mode = Mode.NEWSUB;
      setNewCounter();
      cmbPriority.setValue(Requirement.getPriority("middle"));
      Platform.runLater(new Runnable() {@Override public void run() {txtCounter.requestFocus();}});
    } finally {
      FXrequi.BUSY = false;
    }
  }
  
  private void setNewCounter() {
    String next = "";
    if (selectedTreeItem==null) {
      next = getNextCounter(tree.getRoot());
    } else if (mode==Mode.NEW) {
      TreeItem<Requirement> parent = tree.getSelectionModel().getSelectedItem().getParent();
      next = getNextCounter(parent);
    } else if (mode==Mode.NEWSUB) {
      TreeItem<Requirement> parent = tree.getSelectionModel().getSelectedItem();
      next = getNextCounter(parent);
    }
    txtCounter.setText(next);
  }
  
  private String getNextCounter(TreeItem<Requirement> ti) {
    Collections.sort(ti.getChildren(), new Comparator<TreeItem<Requirement>>() {
      @Override
      public int compare(TreeItem<Requirement> o1, TreeItem<Requirement> o2) {
        return o1.getValue().getCounter().compareTo(o2.getValue().getCounter());
      }
    });
    String max = "";
    for (TreeItem<Requirement> tix: ti.getChildren()) {
      max = tix.getValue().getCounter();
    }
    if (max.isEmpty()) {
      if (mode==Mode.NEW) {
        return "1";
      } else {
        return ti.getValue().getCounter()+".1";
      }
    } else {
      int pos = max.lastIndexOf('.');
      String mainpart = max.substring(0, pos==-1?0:pos);
      String lastpart = max.substring(pos + 1);
      try {
        int n = Integer.parseInt(lastpart)+1;
        return (mainpart.isEmpty()?"":mainpart+".")+n;
      } catch (Exception ex) {
        return ""; //bei ParseException kann keine next number ermittelt werden
      }
    }
  }
  
  private void importXMLAction(ActionEvent event) {
    log.fine("aufgerufen");
    FXrequi.BUSY = true;
    try {
      //XML wird immer unterhalb des markierten Knotens geladen
      TreeItem<Requirement> ti = tree.getSelectionModel().getSelectedItem();
      if (ti==null) {
        ti = tree.getRoot();
      }
      FileChooser fileChooser = new FileChooser();
      fileChooser.setTitle("Open Resource File");
      fileChooser.getExtensionFilters().addAll(
              new ExtensionFilter("XML-Dateien", "*.xml"),
              new ExtensionFilter("All Files", "*.*"));
      File selectedFile = fileChooser.showOpenDialog(FXrequi.APP.getStage());
      if (selectedFile != null) {
        log.fine("Datei="+selectedFile.getName());
        importXML(ti, selectedFile);
      }
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim XML-Import!", ex);
      FXrequi.APP.showError("Fehler beim XML-Import!");
    } finally {
      FXrequi.BUSY = false;
    }
  }
  
  private void exportXMLAction(ActionEvent event) {
    log.fine("aufgerufen");
    FXrequi.BUSY = true;
    try {
      //XML wird immer unterhalb des markierten Knotens geladen
      TreeItem<Requirement> ti = tree.getSelectionModel().getSelectedItem();
      if (ti==null) {
        ti = tree.getRoot();
      }
      FileChooser fileChooser = new FileChooser();
      fileChooser.setTitle("Open Resource File");
      fileChooser.getExtensionFilters().addAll(
              new ExtensionFilter("XML-Dateien", "*.xml"),
              new ExtensionFilter("All Files", "*.*"));
      File selectedFile = fileChooser.showSaveDialog(FXrequi.APP.getStage());
      if (selectedFile != null) {
        log.fine("Datei="+selectedFile.getName());
        exportXML(ti, selectedFile);
      }
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim XML-Export!", ex);
      FXrequi.APP.showError("Fehler beim XML-Export!");
    } finally {
      FXrequi.BUSY = false;
    }
  }
  
  private void editRequirementAction(ActionEvent event) {
    log.fine("aufgerufen");
    FXrequi.BUSY = true;
    try {
      panNavigation.setDisable(true);
      panList.setDisable(true);
      panDetails.setDisable(false);
      mode = Mode.EDIT;
      Platform.runLater(new Runnable() {@Override public void run() {txtCounter.requestFocus();}});
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim Bearbeiten der Anforderung!", ex);
      FXrequi.APP.showException("Fehler beim Bearbeiten der Anforderung!", ex);
    } finally {
      FXrequi.BUSY = false;
    }
  }
  
  private void deleteRequirementAction(ActionEvent event) {
    log.fine("aufgerufen");
    FXrequi.BUSY = true;
    try {
      TreeItem<Requirement> ti = tree.getSelectionModel().getSelectedItem();
      Action action = Dialogs.create().owner(FXrequi.APP.getStage()).masthead(null).
          actions(Dialog.ACTION_NO, Dialog.ACTION_YES).
          title("Bestätigung").message("Wollen Sie die Anforderung "+ti.getValue().getCounter()+" wirklich löschen?").showConfirm();
      log.fine("action.text="+action.getText()+", longtext="+action.getLongText());
      if (action==Dialog.ACTION_YES) {
        DataStore ds = DataStore.getInstance();
        ds.deleteRequirement(ti.getValue().getId());
        TreeItem<Requirement> parent = ti.getParent();
        if (parent!=null) {
          parent.getChildren().remove(ti);
        }
        tree.getSelectionModel().clearSelection();
      }
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim Loeschen der Anforderung!", ex);
      FXrequi.APP.showError("Fehler beim Loeschen der Anforderung!");
    } finally {
      FXrequi.BUSY = false;
    }
  }
  
  private void saveAction(ActionEvent event) {
    log.fine("aufgerufen");
    try {
      form.resetErrors();
      if (!form.validate()) {
        return;
      }
      DataStore ds = DataStore.getInstance();
      Requirement req = null;
      switch (mode) {
        case NEW:
          req = getFormData();
          req.setProjectName(FXrequi.APP.getSelectedProject().getName());
          req.setDateCreated(new Date());
          req.setUserCreated(ds.getLoginUser().getName());
          TreeItem<Requirement> ti = new TreeItem<>(req);
          if (selectedTreeItem==null) {
            tree.getRoot().getChildren().add(ti);
          } else {
            TreeItem<Requirement> parent = tree.getSelectionModel().getSelectedItem().getParent();
            if (parent.getValue()==null) {
              tree.getRoot().getChildren().add(ti);
            } else {
              req.setParentId(parent.getValue().getId());
              parent.getChildren().add(ti);
            }
          }
          tree.getSelectionModel().select(ti);
          setFormData(selectedTreeItem==null?null:selectedTreeItem.getValue());
          ds.insertRequirement(req);
          break;
        case NEWSUB:
          req = getFormData();
          req.setProjectName(FXrequi.APP.getSelectedProject().getName());
          req.setDateCreated(new Date());
          req.setUserCreated(ds.getLoginUser().getName());
          TreeItem<Requirement> ti2 = new TreeItem<>(req);
          if (selectedTreeItem==null) {
            tree.getRoot().getChildren().add(ti2);
          } else {
            TreeItem<Requirement> parent2 = tree.getSelectionModel().getSelectedItem();
            req.setParentId(parent2.getValue().getId());
            parent2.getChildren().add(ti2);
            parent2.setExpanded(true);
          }
          tree.getSelectionModel().select(ti2);
          setFormData(selectedTreeItem==null?null:selectedTreeItem.getValue());
          ds.insertRequirement(req);
          break;
        case EDIT:
          req = getFormData();
          TreeItem<Requirement> old = tree.getSelectionModel().getSelectedItem();
          req.setId(old.getValue().getId());
          req.setProjectName(old.getValue().getProjectName());
          req.setParentId(old.getValue().getParentId());
          req.setDateCreated(old.getValue().getDateCreated());
          req.setUserCreated(old.getValue().getUserCreated());
          old.setValue(req);
          ds.updateRequirement(req);
          break;
        default:
          throw new AssertionError();
      }
      panNavigation.setDisable(false);
      panList.setDisable(false);
      panDetails.setDisable(true);
      Platform.runLater(new Runnable() {@Override public void run() {tree.requestFocus();}});
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim Prüfen und Speichern der Anforderung!", ex);
      FXrequi.APP.showException("Fehler beim Prüfen und Speichern der Anforderung!", ex);
    }
  }

  private void cancelAction(ActionEvent event) {
    log.fine("aufgerufen");
    form.resetErrors();
    panNavigation.setDisable(false);
    panList.setDisable(false);
    panDetails.setDisable(true);
    setFormData(tree.getSelectionModel().getSelectedItem().getValue());
    Platform.runLater(new Runnable() {@Override public void run() {tree.requestFocus();}});
  }

  private void showProjectAction(ActionEvent event) {
    log.fine("aufgerufen");
    try {
      FXrequi.APP.setTopPane(null);
      FXMLLoader ldr = new FXMLLoader(this.getClass().getResource("ProjectView.fxml"));
      AnchorPane pane = (AnchorPane)ldr.load();
      FXrequi.APP.setMainPane(pane);
    } catch (Exception ex) {
      log.log(Level.SEVERE, "Fehler beim Erzeugen der Projektansicht!", ex);
      FXrequi.APP.showException("Fehler beim Erzeugen der Projektansicht!", ex);
    }
  }
  
  private void showTestsAction(ActionEvent event) {
    log.fine("aufgerufen");
    //TODO 2 Testansicht anzeigen
    
  }
  
  private void selectAction(ActionEvent event) {
    log.fine("aufgerufen");
    tree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
  }
  
  private void releaseAction(ActionEvent event) {
    log.fine("aufgerufen");
    DataStore ds = null;
    try {
      ds = DataStore.getInstance();
      for (TreeItem<Requirement> ti: tree.getSelectionModel().getSelectedItems()) {
        ds.setRequirementReleased(ti.getValue().getId());
        ti.getValue().setState(Requirement.states.get(1));
      }
      ds.commit();
      TreeItem<Requirement> ti = new TreeItem<>();
      ds.getRequirementsTree(ti, FXrequi.APP.getSelectedProject().getName(), null);
      tree.setRoot(ti);
    } catch (Exception ex) {
      if (ds!=null) try{ds.rollback();} catch(Exception e){}
      log.log(Level.SEVERE, "Fehler bei Freigabe der Anforderung!", ex);
      FXrequi.APP.showException("Fehler beim Erzeugen der Projektansicht!", ex);
    }
    tree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
  }
  
  private void copyAction(ActionEvent event) {
    log.fine("aufgerufen");
    //TODO 1 Anforderungen kopieren
    
  }
  
  private void pasteAction(ActionEvent event) {
    log.fine("aufgerufen");
    //TODO 1 Anforderungen einfügen
    
  }
  
  private void moveAction(ActionEvent event) {
    log.fine("aufgerufen");
    //TODO 1 Anforderungen verschieben
    
  }
  
  private void setFlowList(String values, List<String> list) {
    log.fine("aufgerufen");
    if (values!=null && !values.isEmpty()) {
      String[] parts = values.split(",");
      for (String val: parts) {
        Editable elm = new Editable(val,list);
        flowKeywords.getChildren().add(elm);
        elm.label.textProperty().addListener((observable, oldValue, newValue) -> {
          if (newValue.isEmpty()) {
            flowKeywords.getChildren().remove(elm);
          }
        });
      }
    }
    flowKeywords.setOnMouseClicked(event -> {
      log.fine("flowKeywords clicked, checking double click...");
      if (event.getClickCount()==2) {
        log.fine("clicked double");
        Editable elm = new Editable(null,list);
        flowKeywords.getChildren().add(elm);
        elm.label.textProperty().addListener((observable, oldValue, newValue) -> {
          if (newValue.isEmpty()) {
            flowKeywords.getChildren().remove(elm);
          }
        });
        Event.fireEvent(elm.label, new MouseEvent(MouseEvent.MOUSE_CLICKED, 
                0, 0, 0, 0, MouseButton.PRIMARY, 1, true, true, true, true, 
                true, true, true, true, true, true, null));
      }
    });
  }
  
  private String getFlowList() {
    log.fine("aufgerufen");
    StringBuilder ret = new StringBuilder();
    for (Node node: flowKeywords.getChildren()) {
      Label lab = ((Editable)node).label;
      if (lab.getText()!=null && !lab.getText().isEmpty()) {
        ret.append(lab.getText() + ",");
      }
    }
    if (ret.length()>0) {
      ret.deleteCharAt(ret.length()-1);
    }
    return ret.toString();
  }
  
  /**
   * stellt ein editierbares Label zur Verfügung; wenn auf das Label geklickt wird,
   * wird darüber ein gleichgroßes TestField erzeugt, in dem der Test bearbeitet 
   * werden kann; wird im TextField ENTER gedrückt oder der Fokus verlassen, so
   * wird der Text ins Label zurück geschrieben und das TextField entfernt.
   */
  class Editable extends StackPane {
    private Label label;
    private List<?> list;
    /**
     * erzeugt ein editierbares Label.
     * @param value  der Text für das Label; NULL wenn kein Text angezeigt werden soll
     */
    public Editable(String value, List<String> list) {
      super();
      log.fine("aufgerufen");
      this.list = list;
      label = new Label(value);
      label.setMinWidth(30);
      label.getStyleClass().add("tool-window");
      this.getChildren().add(label);
      label.setOnMouseClicked(mouseEvent -> {
        log.fine("keyword label* clicked, checking primary click...");
        if (mouseEvent.getButton()==MouseButton.PRIMARY) {
          log.fine("primary* clicked");
          Label l = (Label)mouseEvent.getSource();
          if (list==null) {
            log.finer("create textfield");
            TextField tf = new TextField(l.getText());
            tf.setPrefWidth(l.getWidth());
            this.getChildren().add(tf);
            Platform.runLater(new Runnable() {@Override public void run() {tf.requestFocus();}});
            tf.setOnAction(actionEvent -> {
              log.fine("keyword textfield* pressed enter");
              l.setText(tf.getText());
              this.getChildren().remove(tf);
            });
//            tf.textProperty().addListener((observable, oldValue, newValue) -> {
//              log.fine("keyword textfield* changed");
//              l.setText(newValue);
//            });
            tf.focusedProperty().addListener((observable, oldValue, newValue) -> {
              log.fine("keyword textfield* focus changed: new="+newValue);
              if (newValue==false) {
                l.setText(tf.getText());
                this.getChildren().remove(tf);
              }
            });
          } else {
            log.finer("create combobox");
            ComboBox cb = new ComboBox(FXCollections.observableArrayList(list));
            for (String s: list) {
              if (s.equals(l.getText())) {
                log.finer("label text found, selecting in combobox");
                cb.setValue(s);
              }
            }
            this.getChildren().add(cb);
            Platform.runLater(new Runnable() {@Override public void run() {cb.requestFocus();}});
            cb.setOnAction(actionEvent -> {
              log.fine("keyword combobox* pressed enter");
              l.setText(cb.getValue()==null?"":cb.getValue().toString());
              this.getChildren().remove(cb);
            });
            cb.focusedProperty().addListener((observable, oldValue, newValue) -> {
              log.fine("keyword combobox* focus changed: new="+newValue);
              if (newValue==false) {
                l.setText(cb.getValue()==null?"":cb.getValue().toString());
                this.getChildren().remove(cb);
              }
            });
            cb.setOnKeyPressed(e -> {
              if (e.getCode().equals(KeyCode.BACK_SPACE)) {
                cb.getSelectionModel().clearSelection();
              }
            });
          }
        }
      });
    }
    public void setText(String text) {
      label.setText(text);
    }
    public String getText() {
      return label.getText();
    }
  }
  
  private void exportXML(TreeItem<Requirement> ti, File file) throws Exception {
    log.fine("aufgerufen: file="+file.getAbsolutePath());
    Serializer serializer = new Persister();
    DataStore ds = DataStore.getInstance();
    if (ti.getValue()==null) {
      log.fine("XML ab erster Ebene erzeugen");
      ProjectX obj = ds.getProjectXById(FXrequi.APP.getSelectedProject().getName());
      serializer.write(obj, file);
    } else {
      log.fine("XML unterhalb der Anfoderung des Knotens erzeugen");
      List<RequirementX> obj = ds.getRequirementXSubList(ti.getValue().getProjectName(), ti.getValue().getId());
      serializer.write(obj, file);
    }
  }
  
  private void importXML(TreeItem<Requirement> ti, File selectedFile) {
    log.fine("aufgerufen: selectedFile="+selectedFile.getAbsolutePath());
    //Anforderungen werden immer unterhalb des gegebenen Knotens geladen
    if (ti.getValue()==null) {
      log.fine("XML ab erster Ebene laden");
      
    } else {
      log.fine("XML unter die Anfoderung des Knotens laden");
      
    }
  }
  
  
  
}
